package mk.finki.chemistry.dice;

import org.jgap.BulkFitnessFunction;
import org.jgap.Configuration;
import org.jgap.FitnessFunction;
import org.jgap.IChromosome;
import org.jgap.IInitializer;
import org.jgap.Population;
import org.jgap.audit.IEvolutionMonitor;
import org.jgap.event.GeneticEvent;
import org.jgap.impl.GABreeder;

public class DiceBreeder extends GABreeder {

	private transient Configuration m_lastConf;

	private transient Population m_lastPop;

	int stepStopped = 0;

	public Population evolve1(Population a_pop, Configuration a_conf)
			throws NoValueChromosomesException {
		Population pop = a_pop;

		int previousStepStopped = stepStopped;
		stepStopped = 0;

		int originalPopSize = a_conf.getPopulationSize();
		boolean monitorActive = a_conf.getMonitor() != null;
		IChromosome fittest = null;

		if (previousStepStopped != 1) {
			// If first generation: Set age to one to allow genetic operations,
			// see CrossoverOperator for an illustration.
			// ----------------------------------------------------------------
			if (a_conf.getGenerationNr() == 0) {
				int size = pop.size();
				for (int i = 0; i < size; i++) {
					IChromosome chrom = pop.getChromosome(i);
					chrom.increaseAge();
				}
			}

			System.out.println("updateChromosomes 1");
			updateChromosomes1(pop, a_conf);

			// Apply certain NaturalSelectors before GeneticOperators will be
			// executed.
			// ------------------------------------------------------------------------
			pop = applyNaturalSelectors(a_conf, pop, true);

			// Execute all of the Genetic Operators.
			// -------------------------------------
			applyGeneticOperators(a_conf, pop);

			// Reset fitness value of genetically operated chromosomes.
			// Normally, this should not be necessary as the Chromosome class
			// initializes each newly created chromosome with
			// FitnessFunction.NO_FITNESS_VALUE. But who knows which Chromosome
			// implementation is used...
			// ----------------------------------------------------------------
			int currentPopSize = pop.size();
			for (int i = originalPopSize; i < currentPopSize; i++) {
				IChromosome chrom = pop.getChromosome(i);
				chrom.setFitnessValueDirectly(FitnessFunction.NO_FITNESS_VALUE);
				// Mark chromosome as new-born.
				// ----------------------------
				chrom.resetAge();
				// Mark chromosome as being operated on.
				// -------------------------------------
				chrom.increaseOperatedOn();
			}

			// Increase age of all chromosomes which are not modified by genetic
			// operations.
			// -----------------------------------------------------------------
			int size = Math.min(originalPopSize, currentPopSize);
			for (int i = 0; i < size; i++) {
				IChromosome chrom = pop.getChromosome(i);
				chrom.increaseAge();
				// Mark chromosome as not being operated on.
				// -----------------------------------------
				chrom.resetOperatedOn();
			}
			stepStopped = 1;
			System.out.println("updateChromosomes 2");
			updateChromosomes1(pop, a_conf);
			stepStopped = 0;
		}
		// Apply certain NaturalSelectors after GeneticOperators have been applied.
		// ------------------------------------------------------------------------
		pop = applyNaturalSelectors(a_conf, pop, false);

		// Fill up population randomly if size dropped below specified percentage
		// of original size.
		// ----------------------------------------------------------------------
		if (a_conf.getMinimumPopSizePercent() > 0) {
			int sizeWanted = a_conf.getPopulationSize();
			int popSize;
			int minSize = (int) Math.round(sizeWanted * (double) a_conf.getMinimumPopSizePercent() / 100);
			popSize = pop.size();
			if (popSize < minSize) {
				IChromosome newChrom;
				IChromosome sampleChrom = a_conf.getSampleChromosome();
				Class sampleChromClass = sampleChrom.getClass();
				IInitializer chromIniter = a_conf.getJGAPFactory().getInitializerFor(sampleChrom,
						sampleChromClass);
				for (IChromosome c : pop.getChromosomes())
					if (c.getFitnessValueDirectly() == FitnessFunction.NO_FITNESS_VALUE) {
						System.out.println("NoValueChromosomesException 1");
						throw new NoValueChromosomesException(pop);
					}

				while (pop.size() < minSize) {
					try {
						/**
						 * @todo utilize jobs as initialization may be time-consuming as
						 *       invalid combinations may have to be filtered out
						 */
						newChrom = (IChromosome) chromIniter.perform(sampleChrom, sampleChromClass, null);
						if (monitorActive) {
							// Monitor that fitness value of chromosomes is being updated.
							// -----------------------------------------------------------
							a_conf.getMonitor().event(IEvolutionMonitor.MONITOR_EVENT_BEFORE_ADD_CHROMOSOME,
									a_conf.getGenerationNr(), new Object[] { pop, newChrom });
						}
						pop.addChromosome(newChrom);

						for (IChromosome c : pop.getChromosomes())
							if (c.getFitnessValueDirectly() == FitnessFunction.NO_FITNESS_VALUE) {
								System.out.println("NoValueChromosomesException 2");
								throw new NoValueChromosomesException(pop);
							}

					} catch (Exception ex) {
						throw new RuntimeException(ex);
					}
				}
			}
		}

		for (IChromosome c : pop.getChromosomes())
			if (c.getFitnessValueDirectly() == FitnessFunction.NO_FITNESS_VALUE) {

				System.out.println("NoValueChromosomesException 3");
				throw new NoValueChromosomesException(pop);
			}

		IChromosome newFittest = reAddFittest(pop, fittest);
		if (monitorActive && newFittest != null) {
			// Monitor that fitness value of chromosomes is being updated.
			// -----------------------------------------------------------
			a_conf.getMonitor().event(IEvolutionMonitor.MONITOR_EVENT_READD_FITTEST,
					a_conf.getGenerationNr(), new Object[] { pop, fittest });
		}

		// Increase number of generations.
		// -------------------------------
		a_conf.incrementGenerationNr();
		// Fire an event to indicate we've performed an evolution.
		// -------------------------------------------------------
		m_lastPop = pop;
		m_lastConf = a_conf;
		a_conf.getEventManager().fireGeneticEvent(
				new GeneticEvent(GeneticEvent.GENOTYPE_EVOLVED_EVENT, this));
		return pop;
	}

	protected void updateChromosomes1(Population a_pop, Configuration a_conf)
			throws NoValueChromosomesException {
		int currentPopSize = a_pop.size();
		// Ensure all chromosomes are updated.
		// -----------------------------------
		BulkFitnessFunction bulkFunction = a_conf.getBulkFitnessFunction();
		boolean bulkFitFunc = (bulkFunction != null);
		if (!bulkFitFunc) {
			for (int i = 0; i < currentPopSize; i++) {
				IChromosome chrom = a_pop.getChromosome(i);
				if (chrom.getFitnessValueDirectly() == FitnessFunction.NO_FITNESS_VALUE) {
					System.out.println("NoValueChromosomesException updateChromosomes");
					throw new NoValueChromosomesException(a_pop);
				}
			}
		}
	}

}